#include "Terrain/TerrainSerializer23.h"
#include <filesystem>
#include <fstream>
#include <ostream>

#include "Managers/GameManager.h"
#include "Utils/Utils.h"
#include "Utils/BinaryIO.h"

#include "GameThreadPool.hpp"
#include "Log.h"

using namespace PocketSurvival::Engine;

namespace PocketSurvival
{

    struct ChunkSaveData
    {
        int32_t index;
        int32_t offset;
        int32_t size;
    };

    TerrainSerializer23::TerrainSerializer23()
    {
        createRegionCatch(16);
    }

    TerrainSerializer23::~TerrainSerializer23()
    {
        for (int32_t index = 0; index < m_regionCatchVector.size(); index++)
        {
            delete[] m_regionCatchVector[index];
        }
    }

    /***********************************************************************************************************
     * @brief 获取空的 地图区域 ，可用的 地图区域 队列中已经没有 地图区域 的话会向堆申请
     * @return nullptr 内存不足
     ***********************************************************************************************************/
    Region *TerrainSerializer23::getNoDataRegion()
    {
        Region *avaRegion = nullptr;
        if (m_regionQueue.try_dequeue(avaRegion) == false)
        {
            createRegionCatch(16);

            // 增加了缓存都拿不到可用的对象的话，意味着内存用完了
            if (m_regionQueue.try_dequeue(avaRegion) == false)
            {
                return nullptr;
            }
        }
        if (avaRegion == nullptr)
        {
            Log::Error("发生严重错误： 从队列里面取出的 地图区域 对象为空，现在服务器疑似内存泄漏，请尽快重启", Log::LogType::TERRAIN);
            return nullptr;
        }

        // 拿到数据要先清理一遍，免得跟就数据其冲突
        avaRegion->clearAll();
        return avaRegion;
    }

    /***********************************************************************************************************
     * @brief 获取地图区域的块数据，若在地图区域表中不存在的会自动补充进地图区域表里面
     * @param x 地图区域对应的坐标
     * @param y 地图区域对应的坐标
     * @return 非nullptr即为地图区域对象 | nullptr 表示内存不足，也有可能是其它错误
     ***********************************************************************************************************/
    Region *TerrainSerializer23::getRegion(int32_t x, int32_t y)
    {
        std::lock_guard<std::mutex> lk(m_regionMapMutex);

        Byte8Data key;
        key.a = x;
        key.b = y;
        if (m_regionMap.find(key.int64_value) != m_regionMap.end())
        {
            return m_regionMap[key.int64_value];
        }
        std::string regionFileName = fmt::format("{}/Region {},{}.dat", GameSingleton::gameManager->getNowWorldMapsPath(), x, y);

        // 哈希表找不到的话，查找有没有对应的文件数据，有的话就将文件数据加载进哈希表里面，并返回 地图区域 对象
        if (std::filesystem::exists(regionFileName) == true)
        {
            Log::Info(fmt::format("加载地图文件 {}", regionFileName), Log::LogType::TERRAIN);
            std::ifstream inStream(regionFileName, std::ios::in | std::ios::binary);
            if (inStream.is_open() == false)
            {
                inStream.close();
                Log::Error(fmt::format("区块文件 {} 打开失败", regionFileName), Log::LogType::TERRAIN);
                return nullptr;
            }

            Utils::StreamBinaryReader streamReader((std::istream *)(&inStream));

            Region *avaRegion = getNoDataRegion();
            if (avaRegion == nullptr)
            {
                return nullptr;
            }

            // 读取掉头部的 RGN1
            streamReader.readInt32();

            ChunkSaveData saveData[256];
            for (int32_t index = 0; index < 256; index++)
            {
                // 区块的索引
                saveData[index].index = index;
                // offset位置
                saveData[index].offset = streamReader.readInt32();
                // 区块存储所占用的大小
                saveData[index].size = streamReader.readInt32();
            }

            // 原版的存储是无序的，这里需要对其进行排序
            std::sort(saveData, &saveData[256], [](const ChunkSaveData &left, const ChunkSaveData &right)
                      { return left.offset < right.offset; });

            char *readBuff = new char[1024 * 16];

            for (int32_t index = 0; index < 256; index++)
            {
                int32_t saveDataIndex = saveData[index].index;

                if (saveData[index].size > 0)
                {
                    if (saveData[index].size > MAP_CHUNK_SIZE)
                    {
                        Log::Error(fmt::format("发现 地图区域内存 大于 {} 的情况，在地形文件: {} 中的 {}", MAP_CHUNK_SIZE, regionFileName, saveDataIndex), Log::LogType::TERRAIN);
                        avaRegion->regionChunkSize[saveDataIndex] = 0;
                    }
                    else
                    {
                        avaRegion->regionChunkSize[saveDataIndex] = saveData[index].size;

                        // 读取掉数据头部的 CHK1
                        streamReader.readInt32();

                        int32_t readSize = 0;
                        if (index == 255)
                        {
                            // 到最后的话，直接读包的大小就行了
                            readSize = saveData[index].size;
                        }
                        else
                        {
                            // 后面的减4是去掉数据头部，还有就是2个数据包之间是有空隙的，所以需要读取offset差值，而不是直接读包的大小
                            readSize = saveData[index + 1].offset - saveData[index].offset - 4;
                        }

                        streamReader.read(readBuff, readSize);
                        char *buffer = (char *)avaRegion->getChunkBuff(saveDataIndex);
                        memcpy(buffer, readBuff, saveData[index].size);
                    }
                }
            }

            delete readBuff;

            inStream.close();
            m_regionMap[key.int64_value] = avaRegion;
            return avaRegion;
        }

        // 若不存在 地图区域文件，那么直接使用一个空 地图区域 即可
        Region *avaRegion = getNoDataRegion();
        if (avaRegion != nullptr)
        {
            m_regionMap[key.int64_value] = avaRegion;
            return avaRegion;
        }
        return nullptr;
    }

    static int32_t ReadIntFromBuffer(const uint8_t *buffer, int32_t i)
    {
        int32_t value1 = buffer[i];
        int32_t value2 = buffer[i + 1];
        int32_t value3 = buffer[i + 2];
        int32_t value4 = buffer[i + 3];
        return value1 + (value2 << 8) + (value3 << 16) + (value4 << 24);
    }

    static int32_t ReadRleValueFromBuffer(const uint8_t *buffer, int32_t i, int32_t &outValue, int32_t &outCount)
    {
        int32_t value2 = ReadIntFromBuffer(buffer, i);
        int32_t num = Terrain::ExtractLight(value2);
        outValue = Terrain::ReplaceLight(value2, 0);
        if (num < 15)
        {
            outCount = num + 1;
            return i + 4;
        }
        outCount = buffer[i + 4] + 16;
        return i + 5;
    }

    static void WriteIntToBuffer(uint8_t *buffer, int32_t i, int32_t data)
    {
        buffer[i] = (uint8_t)data;
        buffer[i + 1] = (uint8_t)(data >> 8);
        buffer[i + 2] = (uint8_t)(data >> 16);
        buffer[i + 3] = (uint8_t)(data >> 24);
    }

    static int32_t WriteRleValueToBuffer(uint8_t *buffer, int32_t i, int32_t value, int32_t count)
    {
        if (count < 16)
        {
            int32_t data = Terrain::ReplaceLight(value, count - 1);
            WriteIntToBuffer(buffer, i, data);
            return i + 4;
        }
        if (count <= 271)
        {
            int32_t data2 = Terrain::ReplaceLight(value, 15);
            WriteIntToBuffer(buffer, i, data2);
            buffer[i + 4] = (uint8_t)(count - 16);
            return i + 5;
        }
        throw std::logic_error("Count too large.");
    }

    int32_t TerrainSerializer23::compressChunkData(TerrainChunk *terrainChunk, uint8_t *buffer, int32_t buffSize)
    {
        if(terrainChunk == nullptr)
        {
            return 0;
        }
        std::unique_ptr<TerrainData> terrainData = std::make_unique<TerrainData>();

        int32_t num = 0;
        for (int32_t i = 0; i < 16; i++)
        {
            for (int32_t j = 0; j < 16; j++)
            {
                int32_t shaftValueFast = terrainChunk->getShaftValueFast(i, j);
                uint8_t ShortShaftsValue = (uint8_t)((Terrain::ExtractTemperature(shaftValueFast) << 4) | Terrain::ExtractHumidity(shaftValueFast));
                terrainData->terrainBuff[num] = ShortShaftsValue;
                num++;
            }
        }

        int32_t num2 = 0;
        int32_t num3 = -1;
        for(int32_t y = 0; y < 256; y++)
        {
            for(int32_t z = 0; z < 16; z++)
            {
                for(int32_t x = 0; x < 16; x++)
                {
                    int32_t num4 = Terrain::ReplaceLight(terrainChunk->getCellValueFast(x, y , z), 0);
                    if(num2 == 0)
                    {
                        num3 = num4;
                        num2 = 1;
                        continue;
                    }
                    if(num4 != num3)
                    {
        				num = WriteRleValueToBuffer(terrainData->terrainBuff, num, num3, num2);
        				num3 = num4;
        				num2 = 1;
        				continue;
                    }
        			num2++;
        			if (num2 == 271)
        			{
        				num = WriteRleValueToBuffer(terrainData->terrainBuff, num, num3, num2);
        				num2 = 0;
        			}
                }
            }
        }
        if (num2 > 0)
        {
        	num = WriteRleValueToBuffer(terrainData->terrainBuff, num, num3, num2);
        }
        uint32_t outSize = Utils::RawDeflateCompresse((const char *)terrainData->terrainBuff, num, (char *)buffer, buffSize);
        return outSize;
    }

    bool TerrainSerializer23::decompressChunkData(TerrainChunk *terrainChunk, const uint8_t *buffer, int32_t buffSize)
    {
        if (terrainChunk == nullptr)
        {
            return false;
        }
        std::unique_ptr<TerrainData> terrainData = std::make_unique<TerrainData>();
        uint32_t outSize = Utils::RawInflateDecompresse((const char *)buffer, buffSize, (char *)terrainData->terrainBuff, sizeof(terrainData->terrainBuff));

        int32_t targetX = terrainChunk->coords.posX & 0xF;
        int32_t targetY = terrainChunk->coords.posY & 0xF;

        if (outSize >= sizeof(TerrainData) || outSize <= 256)
        {
            return false;
        }

        int32_t num = 0;
        for (int32_t i = 0; i < 16; i++)
        {
            for (int32_t j = 0; j < 16; j++)
            {
                uint8_t ShortShaftsValue = terrainData->terrainBuff[num];
                num++;
                int32_t value = Terrain::ReplaceTemperature(Terrain::ReplaceHumidity(0, ShortShaftsValue & 0xF), ShortShaftsValue >> 4);
                terrainChunk->setShaftValueFast(i, j, value);
            }
        }

        int32_t num2 = 0;
        int32_t num3 = 0;
        int32_t num4 = 0;
        while (num < outSize)
        {
            int32_t value2 = 0;
            int32_t count = 0;
        	num = ReadRleValueFromBuffer(terrainData->terrainBuff, num, value2, count);

        	for (int32_t k = 0; k < count; k++)
        	{
        		terrainChunk->setCellValueFast(num2, num3, num4, value2);
        		num2++;
        		if (num2 >= 16)
        		{
        			num2 = 0;
        			num4++;
        			if (num4 >= 16)
        			{
        				num4 = 0;
        				num3++;
        			}
        		}
        	}
        }

        if (num2 != 0 || num3 != 256 || num4 != 0)
        {
        	throw std::length_error("Corrupt chunk data.");
        }
        return true;
    }

    /***********************************************************************************************************
     * @brief 向 地图区域 中读取 区块 的数据，读取前应该先赋值 区块 中的m_Coords数据
     * @param terrainChunk 区块对象
     * @return true 加载成功 | false 加载失败
     ***********************************************************************************************************/
    bool TerrainSerializer23::loadChunk(TerrainChunk *terrainChunk)
    {
        if (terrainChunk == nullptr)
        {
            return false;
        }
        int32_t regionX = terrainChunk->coords.posX >> 4;
        int32_t regionY = terrainChunk->coords.posY >> 4;
        Region *theRegion = getRegion(regionX, regionY);
        if (theRegion == nullptr)
        {
            return false;
        }
        int32_t targetX = terrainChunk->coords.posX & 0xF;
        int32_t targetY = terrainChunk->coords.posY & 0xF;
        int32_t targetSize = theRegion->getChunkSize(targetX, targetY);
        if (targetSize <= 0)
        {
            return false;
        }

        uint8_t *buffer = theRegion->getChunkBuff(targetX, targetY);
        bool flag = decompressChunkData(terrainChunk, buffer, targetSize);

        if (flag == false)
        {
            Log::Error(fmt::format("解压 区域：{},{} 中的区块: {},{} 错误", regionX, regionY, targetX, targetY));
            return false;
        }
        return true;
    }

    /***********************************************************************************************************
     * @brief 将 区块 保存进对应 地图区域 内
     * @param terrainChunk 区块对象
     * @return true 保存成功 | false 保存失败
     ***********************************************************************************************************/
    bool TerrainSerializer23::saveChunk(TerrainChunk *terrainChunk)
    {
        if(terrainChunk == nullptr)
        {
            return false;
        }
        int32_t regionX = terrainChunk->coords.posX >> 4;
        int32_t regionY = terrainChunk->coords.posY >> 4;
        Region *theRegion = getRegion(regionX, regionY);
        if(theRegion == nullptr)
        {
            return false;
        }
        int32_t chunkX = terrainChunk->coords.posX & 0xF;
        int32_t chunkY = terrainChunk->coords.posY & 0xF;

        uint8_t *buffer = theRegion->getChunkBuff(chunkX, chunkY);
        uint32_t outSize = compressChunkData(terrainChunk, buffer, MAP_CHUNK_SIZE);

        theRegion->setChunkSize(chunkX, chunkY, outSize);
        theRegion->isChange = true;
        
        // Log::info(fmt::format("save chunk : {}", Utils::ToHexString(buffer, outSize)));

        if(outSize <= 0)
        {
            Log::Error(fmt::format("压缩 地图区域 {},{}出错了",terrainChunk->coords.posX, terrainChunk->coords.posY), Log::LogType::TERRAIN);
            return false;
        }
        return false;
    }


    void TerrainSerializer23::internalSaveRegion(Byte8Data theKey, Region *regionItem, std::function<void(Region *)> callback)
    {
        std::string regionFileName = fmt::format("{}/Region {},{}.dat", GameSingleton::gameManager->getNowWorldMapsPath(), theKey.a, theKey.b);
        std::string temporaryFileName = fmt::format("{}/Region {},{}.dat.new", GameSingleton::gameManager->getNowWorldMapsPath(), theKey.a, theKey.b);
        std::ofstream outStream(temporaryFileName, std::ios::out | std::ios::binary);
        if(outStream.is_open())
        {
            int32_t writeChunkPosition = 4 + 256 * 8;

            Utils::StreamBinaryWriter streamWriter((std::ostream *)(&outStream));

            // 写入文件头
            streamWriter.write("RGN1",4);

            for(int32_t index = 0; index < 256; index++)
            {
                int32_t size = regionItem->getChunkSize(index);
                // spdlog::info("index: {}, size: {}", index, size);
                if(size > 0)
                {
                    // 这是offset
                    streamWriter.writeInt32(writeChunkPosition);
                    // 计算下一个offset值
                    writeChunkPosition += size + 4;
                    streamWriter.writeInt32(size);
                }
                else
                {
                    // 没有数据的直接写0即可
                    streamWriter.writeInt32(0);
                    streamWriter.writeInt32(0);
                }
            }
            for(int32_t index = 0; index < 256; index++)
            {
                int32_t size = regionItem->getChunkSize(index);
                uint8_t *buff = regionItem->getChunkBuff(index);
                if(size > 0)
                {
                    // 写入区块头
                    streamWriter.write("CHK1", 4);
                    // 然后再写区块的压缩数据
                    streamWriter.write((const char *)buff, size);
                }
            }
            outStream.flush();
            outStream.close();

            if(std::filesystem::exists(regionFileName) == true && std::filesystem::remove(regionFileName) == false)
            {
                Log::Error(fmt::format("删除旧 地图区域 文件 {} 失败",regionFileName), Log::LogType::TERRAIN);
            }
            std::filesystem::rename(temporaryFileName, regionFileName);
        }
        else
        {
            Log::Error(fmt::format("保存 地图区域 文件到 {} 失败", temporaryFileName), Log::LogType::TERRAIN);
            outStream.close();
        }
        if (callback != nullptr)
        {
            callback(regionItem);
        }
    }

    /***********************************************************************************************************
     * @brief 建所有在哈希表中的 地图区域 保存进本地的map文件中，保存后不会清理哈希表中的 地图区域
     ***********************************************************************************************************/
    void TerrainSerializer23::saveAllRegion()
    {
        std::lock_guard<std::mutex> lk(m_regionMapMutex);

        for(auto it = m_regionMap.begin(); it != m_regionMap.end(); ++it)
        {
            Byte8Data theKey;
            theKey.int64_value = it->first;
            Region *regionItem = it->second;

            if (regionItem->isChange == true)
            {
                regionItem->isChange = false;
                // 多线程保存数据，外部要注意线程安全
                GameSingleton::gameSaveThreadPool->push_task(&TerrainSerializer23::internalSaveRegion, this, theKey, regionItem, nullptr);
            }
        }
    }

    /***********************************************************************************************************
     * @brief 回收所有在哈希表中的 地图区域，并将地图区域放进可用的 地图区域 队列中，注意，该函数不会对 地图区域 进行保存
     ***********************************************************************************************************/
    void TerrainSerializer23::releaseAllRegion()
    {
        std::lock_guard<std::mutex> lk(m_regionMapMutex);

        for (auto it = m_regionMap.begin(); it != m_regionMap.end(); ++it)
        {
            if (it->second != nullptr)
            {
                m_regionQueue.enqueue(it->second);
            }
        }
        m_regionMap.clear();
    }

    /***********************************************************************************************************
     * @brief 创建 地图区域 缓存，创建出来的 地图区域 缓存会被放进可用的 地图区域 队列中
     * @param count 要创建的 地图区域 缓存数量
     ***********************************************************************************************************/
    void TerrainSerializer23::createRegionCatch(uint8_t count)
    {
        Region *regionCatch = new Region[count];
        m_regionCatchVector.push_back(regionCatch);
        for (int32_t index = 0; index < count; index++)
        {
            regionCatch[index].clearAll();
            m_regionQueue.enqueue(&(regionCatch[index]));
        }
    }



    void TerrainSerializer23::saveAndReleaseAllRegion()
    {
        std::lock_guard<std::mutex> lk(m_regionMapMutex);

        for(auto it = m_regionMap.begin(); it != m_regionMap.end(); ++it)
        {
            Byte8Data theKey;
            theKey.int64_value = it->first;
            Region *regionItem = it->second;

            if (regionItem->isChange == true)
            {
                regionItem->isChange = false;
                // 多线程保存数据，外部要注意线程安全
                GameSingleton::gameSaveThreadPool->push_task(&TerrainSerializer23::internalSaveRegion, this, theKey, regionItem, [this](Region *regionItem)
                {
                    m_regionQueue.enqueue(regionItem);
                });
            }
        }
        m_regionMap.clear();
    }
}
